/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * Description: hiudk_lld.c
 * Author: -
 * Create:
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": [COMM]" fmt

#include <linux/module.h>
#include <linux/moduleparam.h>

#include "ossl_knl.h"
#include "hiudk3.h"
#include "hiudk3_lld.h"
#include "hiudk_internal.h"
#include "hiudk_sdk_hw_adpt.h"

#include "securec.h"

#include "vram_common.h"

static bool use_vram = false;
module_param(use_vram, bool, 0644);
MODULE_PARM_DESC(use_vram, "use VRAM or not (only used in sdi_nanoos) - default is false");

static struct hiudk_ctrl g_hiudk_ctrl;
static int hiudk_load_serivce(struct hiudk_sdk *sdk);

static ASYNC_DOMAIN_EXCLUSIVE(g_hiudk_async_domain);
static hiudk_async_ctrl g_hiudk_async_ctrl;

/* 加载全部上层业务 */
static int hiudk_load_serivce(struct hiudk_sdk *sdk)
{
    int i, ret_tmp;
    int ret = 0;

    if (sdk == NULL) {
        pr_err("sdk registration function null\n");
        return -EINVAL;
    }
    if (sdk->hiudk_register_uld == NULL) {
        return 0;
    }

    for (i = 0; i < SERVICE_T_MAX; i++) {
        if (g_hiudk_ctrl.uld_info[i].probe != NULL) {
            ret_tmp = sdk->hiudk_register_uld(i, &g_hiudk_ctrl.uld_info[i]);
            if (ret_tmp != 0) {
                pr_err("Register service type: [%d] to sdk:[%s] error\n", i, sdk->sdk_lable);
                ret = -EINVAL;
            } else {
                pr_info("Server type: [%d] register to sdk: [%s] successfully\n", i, sdk->sdk_lable);
            }
        }
    }
    return ret;
}

/* hiudk_register_uld - register an upper-layer driver
 * @type: uld service type
 * @uld_info: uld callback
 *
 * Registers an upper-layer driver.
 * Traverse existing devices and call @probe to initialize the uld device.
 */
int hiudk_register_uld(enum hinic3_service_type type, struct hinic3_uld_info *uld_info)
{
    int i;
    int ret;

    if (type >= SERVICE_T_MAX) {
        pr_err("Unknown service type: [%d] to register\n", type);
        return -EINVAL;
    }
    mutex_lock(&g_hiudk_ctrl.lock);
    ret = memcpy_s(&g_hiudk_ctrl.uld_info[type], sizeof(g_hiudk_ctrl.uld_info[type]), uld_info, sizeof(*uld_info));
    if (ret != EOK) {
        pr_err("Copy uld info failed, ret: %d\n", ret);
        mutex_unlock(&g_hiudk_ctrl.lock);
        return -EFAULT;
    }

    /* 调用SDK挂接的注册函数，有几个调用几次 */
    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_register_uld != NULL) {
            ret = g_hiudk_ctrl.sdk[i].hiudk_register_uld(type, uld_info);
            if (ret != 0) {
                pr_err("Server type: [%d] register to sdk:[%s] err\n", type, g_hiudk_ctrl.sdk[i].sdk_lable);
            } else {
                pr_info("Server type: [%d] register to sdk: [%s] successfully\n", type, g_hiudk_ctrl.sdk[i].sdk_lable);
            }
        }
    }
    mutex_unlock(&g_hiudk_ctrl.lock);
    return 0;
}

EXPORT_SYMBOL(hiudk_register_uld);


/**
 * get pcie dev_hdl from *udkdev（hwdev）
 * *udkdev: the lld_dev->hwdev
 *
 */
void *hiudk_get_dev_hdl_from_hwdev(void *udkdev)
{
    if (unlikely(!CHECK_UDK_DEV(udkdev))) {
        pr_err("udkdev is error! \n");
        return NULL;
    }

    return GET_DEV_HDL_FROM_HIUDKHWDEV(udkdev);
}
EXPORT_SYMBOL(hiudk_get_dev_hdl_from_hwdev);
/**
 * hiudk_unregister_uld - unregister an upper-layer driver
 * @type: uld service type
 *
 * Traverse existing devices and call @remove to uninstall the uld device.
 * Unregisters an existing upper-layer driver.
 */
void hiudk_unregister_uld(enum hinic3_service_type type)
{
    int i;

    if (type >= SERVICE_T_MAX) {
        pr_err("Unknown type :[%d] of up layer driver to unregister\n", type);
        return;
    }

    while (1) {
        mutex_lock(&g_hiudk_ctrl.sdk_state_lock);
        if (g_hiudk_ctrl.sdk_state == SDK_IN_REMOVE) {
            mutex_unlock(&g_hiudk_ctrl.sdk_state_lock);
            msleep(1000); /* 1000ms */
            continue;
        } else {
            break;
        }
    }

    mutex_lock(&g_hiudk_ctrl.lock);
    mutex_unlock(&g_hiudk_ctrl.sdk_state_lock);

    /* 调用SDK挂接的注册函数，有几个调用几次 */
    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_unregister_uld != NULL) {
            g_hiudk_ctrl.sdk[i].hiudk_unregister_uld(type);
            pr_info("Service type: [%d] unregister from udk: [%s] successfully\n", type, g_hiudk_ctrl.sdk[i].sdk_lable);
        }
    }
    (void)memset_s(&g_hiudk_ctrl.uld_info[type], sizeof(struct hinic3_uld_info), 0, sizeof(struct hinic3_uld_info));
    mutex_unlock(&g_hiudk_ctrl.lock);
    return;
}

EXPORT_SYMBOL(hiudk_unregister_uld);

int hiudk_register_sdk(struct hiudk_sdk *sdk)
{
    unsigned char i;
    int ret;

    if ((sdk == NULL) || (sdk->hiudk_register_uld == NULL) || (sdk->hiudk_unregister_uld == NULL)) {
        pr_err("Sdk register function pointer is NULL \n");
        return -EINVAL;
    }

    mutex_lock(&g_hiudk_ctrl.lock);

    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_register_uld == NULL) {
            g_hiudk_ctrl.sdk[i].hiudk_register_uld = sdk->hiudk_register_uld;
            g_hiudk_ctrl.sdk[i].hiudk_unregister_uld = sdk->hiudk_unregister_uld;
            g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_dev_name = sdk->hiudk_get_lld_dev_by_dev_name;
            g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_dev_name_unsafe = sdk->hiudk_get_lld_dev_by_dev_name_unsafe;
            g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_chip_and_port = sdk->hiudk_get_lld_dev_by_chip_and_port;
            g_hiudk_ctrl.sdk[i].hiudk_get_ppf_dev = sdk->hiudk_get_ppf_dev;
            g_hiudk_ctrl.sdk[i].idx = i;
            sdk->idx = i; /* return current sdk index */
            ret = strncpy_s(g_hiudk_ctrl.sdk[i].sdk_lable, UDK_MAX_SDK_NAME, sdk->sdk_lable, UDK_MAX_SDK_NAME);
            if (ret != EOK) {
                pr_err("Strncpy_s exceution [%d] failed\n", ret);
            }
            g_hiudk_ctrl.sdk[i].sdk_lable[UDK_MAX_SDK_NAME - 1] = 0;

            /* 重新加载已经注册的上层业务 */
            ret = hiudk_load_serivce(sdk);
            if (ret != 0) {
                pr_err("Some service register to sdk: [%s] error\n", g_hiudk_ctrl.sdk[i].sdk_lable);
            }
            break;
        }
    }
    if (i == UDK_MAX_SDK) {
        pr_err("Register SDK : [%s] to UDK error: No empty sdk entities ! \n", sdk->sdk_lable);
        ret = -ENOSPC;
    } else {
        pr_info("Sdk: [%s] register to udk successfully\n", g_hiudk_ctrl.sdk[i].sdk_lable);
        ret = 0;
    }

    mutex_unlock(&g_hiudk_ctrl.lock);
    return ret;
}
EXPORT_SYMBOL(hiudk_register_sdk);

int hiudk_unregister_sdk(unsigned idx)
{
    if (idx >= UDK_MAX_SDK) {
        pr_err("Invalid sdk entity index %d! \n", idx);
        return -EINVAL;
    }

    mutex_lock(&g_hiudk_ctrl.lock);
    pr_info("Sdk: [%s] unregister from udk successfully\n", g_hiudk_ctrl.sdk[idx].sdk_lable);

    if (g_hiudk_ctrl.sdk[idx].hiudk_register_uld != NULL) {
        (void)memset_s(&g_hiudk_ctrl.sdk[idx], sizeof(struct hiudk_sdk), 0, sizeof(struct hiudk_sdk));
    }

    mutex_unlock(&g_hiudk_ctrl.lock);
    return 0;
}
EXPORT_SYMBOL(hiudk_unregister_sdk);

void hiudk_update_sdk_state(enum hiudk_sdk_state sdk_state)
{
    mutex_lock(&g_hiudk_ctrl.sdk_state_lock);
    g_hiudk_ctrl.sdk_state = sdk_state;
    mutex_unlock(&g_hiudk_ctrl.sdk_state_lock);
}
EXPORT_SYMBOL(hiudk_update_sdk_state);

/**
 * @brief hiudk_get_lld_dev_by_dev_name - get lld device by uld device name
 * @param dev_name: uld device name
 * @param type: uld service type, When the type is SERVICE_T_MAX, try to match
 * 	all ULD names to get uld_dev
 *
 * The value of lld_dev reference increases when lld_dev is obtained. The caller needs
 * to release the reference by calling lld_dev_put.
 */
struct hinic3_lld_dev *hiudk_get_lld_dev_by_dev_name(const char *dev_name, enum hinic3_service_type type)
{
    int i;
    struct hinic3_lld_dev *lld_dev = NULL;

    if (type >= SERVICE_T_MAX || dev_name == NULL) {
        pr_err("Unknown dev name:%s or service type %d of up layer driver to register\n", dev_name, type);
        return NULL;
    }
    mutex_lock(&g_hiudk_ctrl.lock);

    /* 调用SDK挂接的注册函数，有几个调用几次 */
    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_dev_name != NULL) {
            lld_dev = g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_dev_name(dev_name, type);
            if (lld_dev != NULL) {
                break;
            }
        }
    }

    mutex_unlock(&g_hiudk_ctrl.lock);
    return lld_dev;
}

EXPORT_SYMBOL(hiudk_get_lld_dev_by_dev_name);

/**
 * @brief hiudk_get_lld_dev_by_dev_name_unsafe - get lld device by uld device name
 * @param dev_name: uld device name
 * @param type: uld service type, When the type is SERVICE_T_MAX, try to match
 *  all ULD names to get uld_dev
 *
 * hiudk_get_lld_dev_by_dev_name_unsafe() is completely analogous to
 * hiudk_get_lld_dev_by_dev_name(), The only difference is that the reference
 * of lld_dev is not increased when lld_dev is obtained.
 *
 * The caller must ensure that lld_dev will not be freed during the remove process
 * when using lld_dev.
 */
struct hinic3_lld_dev *hiudk_get_lld_dev_by_dev_name_unsafe(const char *dev_name, enum hinic3_service_type type)
{
    int i;
    struct hinic3_lld_dev *lld_dev = NULL;

    if (type >= SERVICE_T_MAX || dev_name == NULL) {
        pr_err("Unknown dev name:%s or service type %d of up layer driver to register\n", dev_name, type);
        return NULL;
    }
    mutex_lock(&g_hiudk_ctrl.lock);

    /* 调用SDK挂接的注册函数，有几个调用几次 */
    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_dev_name_unsafe != NULL) {
            lld_dev = g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_dev_name_unsafe(dev_name, type);
            if (lld_dev != NULL) {
                break;
            }
        }
    }

    mutex_unlock(&g_hiudk_ctrl.lock);
    return lld_dev;
}

EXPORT_SYMBOL(hiudk_get_lld_dev_by_dev_name_unsafe);

struct hinic3_lld_dev *hiudk_get_lld_dev_by_chip_and_port(const char *chip_name, u8 port_id)
{
    int i;
    struct hinic3_lld_dev *lld_dev = NULL;

    if (chip_name == NULL) {
        pr_err("Unknown chip name:%s of up layer driver to register\n", chip_name);
        return NULL;
    }
    mutex_lock(&g_hiudk_ctrl.lock);

    /* 调用SDK挂接的注册函数，有几个调用几次 */
    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_chip_and_port != NULL) {
            lld_dev = g_hiudk_ctrl.sdk[i].hiudk_get_lld_dev_by_chip_and_port(chip_name, port_id);
            if (lld_dev != NULL) {
                break;
            }
        }
    }

    mutex_unlock(&g_hiudk_ctrl.lock);
    return lld_dev;
}
EXPORT_SYMBOL(hiudk_get_lld_dev_by_chip_and_port);

void *hiudk_get_ppf_dev(void)
{
    int i;
    void *dev = NULL;

    mutex_lock(&g_hiudk_ctrl.lock);

    /* 调用SDK挂接的注册函数，有几个调用几次 */
    for (i = 0; i < UDK_MAX_SDK; i++) {
        if (g_hiudk_ctrl.sdk[i].hiudk_get_ppf_dev != NULL) {
            dev = g_hiudk_ctrl.sdk[i].hiudk_get_ppf_dev();
            if (dev != NULL) {
                break;
            }
        }
    }

    mutex_unlock(&g_hiudk_ctrl.lock);
    return dev;
}

int hiudk_register_flush_fn(void *lld_dev, hiudk_flush_fn fn)
{
    int i;
    int cur_idx = -1;

    if (lld_dev == NULL) {
        pr_err("Sdk: register flush function para is null.\n");
        return -ENODEV;
    }

    spin_lock(&g_hiudk_async_ctrl.lock);

    for (i = UDK_MAX_PF_NUM - 1; i >= 0; i--) {
        if (g_hiudk_async_ctrl.flush_infos[i].lld_dev == NULL) {
            cur_idx = i;
            break;
        }
    }

    if (cur_idx == -1) {
        spin_unlock(&g_hiudk_async_ctrl.lock);
        pr_err("Sdk: register flush function failed, no available async crtl info.\n");
        return -ENOMEM;
    }

    g_hiudk_async_ctrl.flush_infos[cur_idx].lld_dev = lld_dev;
    g_hiudk_async_ctrl.flush_infos[cur_idx].flush_ops = fn;
    g_hiudk_async_ctrl.flush_infos[cur_idx].ret = 0;

    spin_unlock(&g_hiudk_async_ctrl.lock);

    return 0;
}
EXPORT_SYMBOL(hiudk_register_flush_fn);

int hiudk_unregister_flush_fn(void *lld_dev)
{
    int i;

    if (lld_dev == NULL) {
        pr_err("Sdk: unregister flush function para is null.\n");
        return -ENODEV;
    }

    spin_lock(&g_hiudk_async_ctrl.lock);

    for (i = UDK_MAX_PF_NUM - 1; i >= 0; i--) {
        if (lld_dev == g_hiudk_async_ctrl.flush_infos[i].lld_dev) {
            g_hiudk_async_ctrl.flush_infos[i].lld_dev = NULL;
            g_hiudk_async_ctrl.flush_infos[i].flush_ops = NULL;
            g_hiudk_async_ctrl.flush_infos[i].ret = 0;

            spin_unlock(&g_hiudk_async_ctrl.lock);
            return 0;
        }
    }

    spin_unlock(&g_hiudk_async_ctrl.lock);

    return -ENODEV;
}
EXPORT_SYMBOL(hiudk_unregister_flush_fn);

void hinic3_flush_dev(void *priv_data, async_cookie_t cookie)
{
    hiudk_dev_flush_infos *cur_dev = priv_data;

    if (cur_dev->lld_dev == NULL ||
        cur_dev->flush_ops == NULL) {
            return;
    }

    cur_dev->ret = cur_dev->flush_ops(cur_dev->lld_dev);
}

static int hiudk_os_hotreplace_msg_to_mpu(u8 replace_flag)
{
    int ret;
    void *dev = NULL;

    dev = hiudk_get_ppf_dev();
    if (dev == NULL) {
        pr_err("Get ppf dev failed before os hotreplace.\n");
        return -ENXIO;
    }

    ret = hiudk_set_ppf_tbl_hotreplace_flag(dev, replace_flag);
    if (ret != 0) {
        pr_err("Send mbox to mpu failed in hiudk, ret:%d, flag:%hhu.\n", ret, replace_flag);
        return ret;
    }

    return 0;
}

static int hiudk_notify_pre_update(struct notifier_block *nb,
					unsigned long action,
					void *data)
{
	int ret;

    pr_info("Set driver flag and mpu flag before os hotreplace.\n");
    // set kexec status set to 1, indicate doing kexec
    ret = hi_set_kexec_status(1);
    if (ret != 0) {
        pr_err("Set kexec flag failed before os hotreplace.\n");
        return ret;
    }

    ret = hiudk_os_hotreplace_msg_to_mpu(MPU_OS_HOTREPLACE_FLAG);
    if (ret != 0) {
        pr_err("Send mbox to mpu failed before os hotreplace.\n");
        return ret;
    }

    return 0;
}

static int hiudk_notify_post_update(struct notifier_block *nb,
					 unsigned long action,
					 void *data)
{
	int ret;
    
    pr_info("Clear driver flag and mpu flag after os hotreplace.\n");
    // set kexec status set to 0, indicate kexec done
    ret = hi_set_kexec_status(0);
    if (ret != 0) {
        pr_err("Set kexec flag failed after os hotreplace.\n");
        return ret;
    }

    ret = hiudk_os_hotreplace_msg_to_mpu(0);
    if (ret != 0) {
        pr_err("Send mbox to mpu failed after os hotreplace.\n");
        return ret;
    }

    return 0;
}

static int hiudk3_notify_flush_dev(struct notifier_block *nb,
                                   unsigned long action,
                                   void *data)
{
    int i;

    rtnl_lock();

    for (i = 0; i < UDK_MAX_PF_NUM; i++) {
        if (g_hiudk_async_ctrl.flush_infos[i].lld_dev) {
            async_schedule_domain(hinic3_flush_dev, &g_hiudk_async_ctrl.flush_infos[i], &g_hiudk_async_domain);
        }
    }

    rtnl_unlock();

    return 0;
}

int wait_for_devices_flush(struct notifier_block *nb,
                                  unsigned long action,
                                  void *data)
{
    int i;
    int ret = 0;

    async_synchronize_full_domain(&g_hiudk_async_domain);

    for (i = 0; i < UDK_MAX_PF_NUM; i++) {
        if (g_hiudk_async_ctrl.flush_infos[i].ret != 0) {
            ret = g_hiudk_async_ctrl.flush_infos[i].ret;
            pr_err("Sdk: wait netdev flush done error, ret:%d.\n", ret);
            break;
        }
    }

    return ret;
}
EXPORT_SYMBOL(wait_for_devices_flush);

static struct notifier_block hiudk_notifier_pre_update = {
	.notifier_call	= hiudk_notify_pre_update,
	.next		= NULL,
	.priority	= 0
};

static struct notifier_block hiudk3_notifier_flush_dev = {
    .notifier_call  = hiudk3_notify_flush_dev,
    .next           = NULL,
    .priority       = 0
};

static struct notifier_block hiudk3_notifier_wait_flush_done = {
    .notifier_call  = wait_for_devices_flush,
    .next           = NULL,
    .priority       = 0
};

static struct notifier_block hiudk_notifier_post_update = {
	.notifier_call	= hiudk_notify_post_update,
	.next		= NULL,
	.priority	= 0
};

static __init int hiudk3_init(void)
{
    int err;

    pr_info("%s - version %s\n", HIUDK_DRV_DESC, HINIC3_DRV_VERSION);
    (void)memset_s(&g_hiudk_ctrl, sizeof(g_hiudk_ctrl), 0, sizeof(g_hiudk_ctrl));
    mutex_init(&g_hiudk_ctrl.lock);
    mutex_init(&g_hiudk_ctrl.sdk_state_lock);

    spin_lock_init(&g_hiudk_async_ctrl.lock);

    if (use_vram) {
        lookup_vram_related_symbols();

        err = hi_get_kexec_status();
        if (err != 0) {
            pr_err("Get in kexec status failed, err: %d\n", err);
            goto get_kexec_status_err;
        }

        err = hi_register_nvwa_notifier(PRE_UPDATE_KERNEL, &hiudk_notifier_pre_update);
        if (err != 0) {
            pr_err("Register nvwa pre update failed, err: %d\n", err);
            goto register_pre_update_nvwa_err;
        }

        err = hi_register_nvwa_notifier(POST_UPDATE_KERNEL, &hiudk_notifier_post_update);
        if (err != 0) {
            pr_err("Register nvwa post update failed, err: %d\n", err);
            goto register_post_update_nvwa_err;
        }

        err = hi_register_nvwa_notifier(FLUSH_DURING_KUP, &hiudk3_notifier_flush_dev);
        if (err != 0) {
            pr_err("Register nvwa flush device failed, err: %d\n", err);
            goto register_flush_dev_err;
        }

        err = hi_register_euleros_reboot_notifier(&hiudk3_notifier_wait_flush_done);
        if (err != 0) {
            pr_err("Register wait flush device notify failed, err: %d\n", err);
            goto register_reboot_err;
        }
    }

    set_use_vram_flag(use_vram);

    return 0;

register_reboot_err:
    (void)hi_unregister_nvwa_notifier(FLUSH_DURING_KUP, &hiudk3_notifier_flush_dev);
register_flush_dev_err:
    (void)hi_unregister_nvwa_notifier(POST_UPDATE_KERNEL, &hiudk_notifier_post_update);
register_post_update_nvwa_err:
    (void)hi_unregister_nvwa_notifier(PRE_UPDATE_KERNEL, &hiudk_notifier_pre_update);
register_pre_update_nvwa_err:
get_kexec_status_err:
    spin_lock_deinit(&g_hiudk_async_ctrl.lock);
    return err;
}

static __exit void hiudk3_exit(void)
{
    if (use_vram) {
        (void)hi_unregister_euleros_reboot_notifier(&hiudk3_notifier_wait_flush_done);
        (void)hi_unregister_nvwa_notifier(FLUSH_DURING_KUP, &hiudk3_notifier_flush_dev);
        (void)hi_unregister_nvwa_notifier(POST_UPDATE_KERNEL, &hiudk_notifier_pre_update);
        (void)hi_unregister_nvwa_notifier(PRE_UPDATE_KERNEL, &hiudk_notifier_post_update);
    }
    spin_lock_deinit(&g_hiudk_async_ctrl.lock);

    return;
}

MODULE_LICENSE("GPL");

#ifndef _LLT_TEST_
module_init(hiudk3_init);
module_exit(hiudk3_exit);
#endif
